package com.peking.donations.plugins.oauth2.client;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * GitHub OAuth2 登录方式配置
 * 
 * @author Vigor Yuan
 */
public class OAuth2Configurer {

    private BeanFactory beanFactory;

    private String configName;

    private OAuth2Resource customerOAuth2Resource;

    public OAuth2Configurer(BeanFactory beanFactory, String configName) {
        this.beanFactory = beanFactory;
        this.configName = configName;
    }

    public void configure(HttpSecurity http) throws Exception {
        this.customerOAuth2Resource = beanFactory.getBean(getResourceBeanName(), OAuth2Resource.class);

        SessionAuthenticationStrategy sessionAuthenticationStrategy = http
                .getSharedObject(SessionAuthenticationStrategy.class);
        OAuth2ClientAuthenticationProcessingFilter ssoFilter = oauth2SsoFilter();

        if (null != sessionAuthenticationStrategy) {
            ssoFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
        }

        http.addFilterAfter(ssoFilter, AbstractPreAuthenticatedProcessingFilter.class);
        addAuthenticationEntryPoint(http);
    }

    /**
     * 
     * oauth2 认证 入口点定义
     * 
     * @param http
     * @throws Exception
     */
    private void addAuthenticationEntryPoint(HttpSecurity http) throws Exception {
        ExceptionHandlingConfigurer<HttpSecurity> exceptions = http.exceptionHandling();
        ContentNegotiationStrategy contentNegotiationStrategy = http.getSharedObject(ContentNegotiationStrategy.class);
        if (contentNegotiationStrategy == null) {
            contentNegotiationStrategy = new HeaderContentNegotiationStrategy();
        }

        // 匹配 'application/xhtml+xml','text/html','text/plain'
        MediaTypeRequestMatcher preferredMatcher = new MediaTypeRequestMatcher(contentNegotiationStrategy,
                MediaType.APPLICATION_XHTML_XML, new MediaType("image", "*"), MediaType.TEXT_HTML,
                MediaType.TEXT_PLAIN);

        preferredMatcher.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
        List<RequestMatcher> requestMatcherList = new ArrayList<RequestMatcher>();

        RequestHeaderRequestMatcher headerRequestMatcher = new RequestHeaderRequestMatcher("oauth2", "wechart");
        requestMatcherList.add(preferredMatcher);
        requestMatcherList.add(headerRequestMatcher);

        AndRequestMatcher andRequestMatcher = new AndRequestMatcher(requestMatcherList);
        exceptions.defaultAuthenticationEntryPointFor(
                new LoginUrlAuthenticationEntryPoint(customerOAuth2Resource.getLoginPath()), preferredMatcher);
        // When multiple entry points are provided the default is the first one
        exceptions.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
                andRequestMatcher);
    }

    /**
     * 定义Spring-security过滤器
     * 
     * @return
     */
    private OAuth2ClientAuthenticationProcessingFilter oauth2SsoFilter() {
        OAuth2RestOperations restTemplate = this.beanFactory.getBean(getRestTemplateName(), OAuth2RestOperations.class);
        ResourceServerTokenServices tokenServices = this.beanFactory.getBean(getTokenServiceName(),
                ResourceServerTokenServices.class);
        OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(
                customerOAuth2Resource.getLoginPath());
        filter.setRestTemplate(restTemplate);
        filter.setTokenServices(tokenServices);

        // 处理oauth2 认证成功回调
        if (this.beanFactory.containsBean(getSuccessHandlerName())) {
            AuthenticationSuccessHandler successHandler = this.beanFactory.getBean(getSuccessHandlerName(),
                    AuthenticationSuccessHandler.class);
            filter.setAuthenticationSuccessHandler(successHandler);
        }

        // 处理oauth2 认证失败回调
        if (this.beanFactory.containsBean(getFailureHandlerName())) {
            AuthenticationFailureHandler failureHandler = this.beanFactory.getBean(getFailureHandlerName(),
                    AuthenticationFailureHandler.class);
            filter.setAuthenticationFailureHandler(failureHandler);
        }
        return filter;
    }

    private String getResourceBeanName() {
        return this.configName + "OAuth2Resource";
    }

    private String getRestTemplateName() {
        return this.configName + "OAuth2RestTemplate";
    }

    private String getTokenServiceName() {
        return this.configName + "TokenService";
    }

    private String getSuccessHandlerName() {
        return this.configName + "SuccessHandler";
    }

    private String getFailureHandlerName() {
        return this.configName + "FailureHandler";
    }
}
